var Playlist = Class.create({
    xspf: null,
    container: null,
    playerID: null,
    tracks: [],
    isPlaying: false,
    trackIndex: -1,
    // methods
    /**
     * @param xspf string path to the xspf view of the playlist
     * @param playerID string The id of the element the player
     *        swf should be loaded into.
     * @param container Element|string An id or Element which
     *        encapsulates the playlist items.
     * @param trackClass null|Class The class that should be 
     *        created for each track that is found within 
     *        container.
     */
    initialize: function(xspf, playerID, container, trackClass) {
        if(!trackClass) trackClass = Playlist.Track;
        this.xspf = xspf;
        this.playerID = playerID;
        this.container = $(container);
        // find all the li.tracks
        this.container.select('ol li.track').each(function(track) {
            this.tracks.push(new trackClass(track, this));
        }.bind(this));
        this.initPlayer();
    },
    initPlayer: function() {
        var flashvars = {
            type: "xml",
            shuffle: "false",
            repeat: "list",
            file: this.xspf        
        }
        var params = {
            allowscriptaccess: "always"
        }
        var attributes = {
          id: this.playerID,
          name: this.playerID,
          styleclass: "flash_player"
        }
        swfobject.embedSWF('/static/playlist/mediaplayer.swf', this.playerID, "0", "0", "8.0.0", false, flashvars, params, attributes);
    },
    /**
     * on first play we need to register the callback
     * functions for this.getPlayer() to call on this
     */
    registerPlayerEvents: function() {
        var rand = Math.ceil(Math.random() * 1000000);
        var id = this.playerID + '_' + rand;
        window[id] = this;
        var player = this.getPlayer();
        
        player.addModelListener('TIME', id + '.timeCallback');
        player.addModelListener('STATE', id + '.stateCallback');
        player.addControllerListener('ITEM', id + '.itemCallback');
    },
    getPlayer: function() {
        if(navigator.appName.indexOf("Microsoft") != -1) {
            return window[this.playerID];
        } else {
            return document[this.playerID];
        }
    },
    skipTo: function(track) {
        if(this.trackIndex==-1) {
            this.registerPlayerEvents();
        }
        this.isPlaying = false;
        var currentTrack = this.getCurrentTrack();
        if(currentTrack) {
            currentTrack.setState('COMPLETED');
        }
        this.trackIndex = this.tracks.indexOf(track);
        this.getPlayer().sendEvent('ITEM', this.trackIndex);
        this.play(track);
    },
    /**
     * callback from this.getPlayer()
     */
    timeCallback: function(msg) {
        this.getCurrentTrack().updatePosition(msg.position);
    },
    /**
     * callback from this.getPlayer()
     */
    stateCallback: function(msg) {
        var track = this.getCurrentTrack();
        var state = msg.newstate;
        if(state!='PLAYING') {
            /* the PLAYING state annoyingly gets sent
             * before the itemCallback when moving to
             * the next track, so we do that elsewhere 
             * manually */
            track.setState(state);
        }
    },
    /**
     * callback from this.getPlayer()
     */
    itemCallback: function(msg) {
         var index = msg.index;
         var track = this.getCurrentTrack();
         if(index != this.trackIndex) {
             this.trackIndex = index;
             track = this.getCurrentTrack();
         }
         track.setState('PLAYING');     
    },
    play: function(track) {
        var newTrackIndex = this.tracks.indexOf(track);
        if(newTrackIndex != this.trackIndex) {
            this.skipTo(track);
        } else if (this.isPlaying) {
            this.pause();
        } else {
            //final point of any playback.
            //skipTo sets playing to false, sets trackIndex
            //to track, and the calls play(track)
            this.unpause();
        }
    },
    getCurrentTrack: function() {
        return this.trackIndex >= 0
            ? this.tracks[this.trackIndex]
            : null;
    },
    pause: function() {
        this.isPlaying = false;
        this.getPlayer().sendEvent('PLAY', false);
        this.getCurrentTrack().setState('PAUSED');
    },
    unpause: function() {
        this.isPlaying = true;
        this.getPlayer().sendEvent('PLAY', true);
        this.getCurrentTrack().setState('PLAYING');
    }
});

Playlist.Track = Class.create({
    container: null,
    playlist: null,
    initialize: function(container, playlist) {
        this.playlist = playlist;
        this.container = container;
        this.container.observe('mouseover', this.mouseover.bind(this));
        this.container.observe('mouseout', this.mouseout.bind(this));
        this.container.observe('click', this.click.bind(this));
        this.cancelLinks();
    },
    /**
     * links within the click area of this.container
     * need to maintain their default behaviour, so we
     * need to cancel the bubbling of the click event
     * before it reaches this.container for each anchor
     */
    cancelLinks: function() {
        var links = this.container.select('a');
        links.each(function(link) {
            link.observe('click', this.cancelLink);
        }.bind(this));
    },
    cancelLink: function(e) {
        e.cancelBubble = true;
        blur();
    },
    /**
     * event callback for container
     */
    mouseover: function(e) {
        this.focus();
    },
    /**
     * event callback for container
     */
    mouseout: function(e) {
        this.unfocus();
    },
    /**
     * event callback for container
     */
    click: function(e) {
        this.action();
    },
    
    focus: function() {
        this.container.addClassName('trackHover');
    },
    unfocus: function() {
        this.container.removeClassName('trackHover');
    },
    action: function() {
        this.playlist.play(this);
    },
    
    /**
     * callbacks
     */
    
    updatePosition: function(position) {
        var position = parseInt(Math.round(position));
        var minutes = Math.floor(position / 60);
        var seconds = position % 60;
        if(seconds < 10 && minutes > 0) seconds = '0' + seconds;
        var labelText = minutes > 0
            ? minutes + ':' + seconds
            : seconds;
        var positionContainer = this.container.down('.position');
        if(positionContainer) {
            positionContainer.update(labelText);
        }
    },
    /**
     * @param state string PLAYING, PAUSED, COMPLETED
     */
    setState: function(state) {
        if(state=='PLAYING') {
            this.container.down('.position').update('—');
            this.container.removeClassName('trackPaused');
            this.container.addClassName('trackPlaying');
        } else if(state=='PAUSED') {
            this.container.removeClassName('trackPlaying');
            this.container.addClassName('trackPaused');
        } else if(state=='COMPLETED') {
            this.container.removeClassName('trackPlaying');
            this.container.removeClassName('trackPaused');
        }
    }
});

/**
 * Simple wrapper for the default playlist html snippet
 * if it doesn't already exist within the dom.
 *
 * @param container id or Element of where the content
 *        html should be inserted into.
 * @param url String url to fetch the playlist html snippet
 * @param callback callback function to call on completion
 */
Playlist.load = function(container, url, callback) {
    container = $(container);
    new Ajax.Request(url, {
        method: 'get',
        onSuccess: function(transport) {
            var html = transport.responseText;
            container.update(html);
            callback();
        }
        //todo: error cases
    });
}